package com.pkk.penguin.components.starter.rpc.crypto.handler;

import static com.pkk.penguin.components.starter.rpc.crypto.constants.CommonConstants.SECRET_PROPERTIES_PREFIX;

import com.pkk.penguin.components.starter.rpc.crypto.annotation.DecryptBody;
import com.pkk.penguin.components.starter.rpc.crypto.annotation.IgnoreDecryptBody;
import com.pkk.penguin.components.starter.rpc.crypto.properties.SecretProperties;
import com.pkk.penguin.components.starter.rpc.crypto.service.CryptoService;
import com.pkk.components.util.io.StreamUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

/**
 * @author luffy
 * @description: 全局统一解密request
 * @date 2019-11-15 14:04
 */
@Slf4j
@Order(99)
@RestControllerAdvice
@ConditionalOnProperty(prefix = SECRET_PROPERTIES_PREFIX, name = "enabled", havingValue = "true")
public class DecryptRequestHandler implements RequestBodyAdvice {

  @Autowired
  private CryptoService cryptoService;

  @Resource
  private SecretProperties secretProperties;


  /**
   * Invoked first to determine if this interceptor applies.
   * @param methodParameter the method parameter
   * @param targetType the target type, not necessarily the same as the method parameter type, e.g. for {@code
   *     HttpEntity<String>}.
   * @param converterType the selected converter type
   * @return whether this interceptor should be invoked or not
   */
  @Override
  public boolean supports(MethodParameter methodParameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    if (secretProperties.isScanAnnotation()) {
      return methodParameter.hasParameterAnnotation(RequestBody.class) && methodParameter.hasMethodAnnotation(
          DecryptBody.class);
    } else {
      return methodParameter.hasParameterAnnotation(RequestBody.class) && !methodParameter.hasMethodAnnotation(
          IgnoreDecryptBody.class);
    }
  }

  /**
   * Invoked second before the request body is read and converted.
   * @param inputMessage the request
   * @param parameter the target method parameter
   * @param targetType the target type, not necessarily the same as the method parameter type, e.g. for {@code
   *     HttpEntity<String>}.
   * @param converterType the converter used to deserialize the body
   * @return the input request or a new instance, never {@code null}
   */
  @Override
  public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
    return new HttpInputMessage() {
      @Override
      public InputStream getBody() throws IOException {
        log.debug("此处进行解密数据");
        //数据解密
        String encrypt = StreamUtil.inputStream2String(inputMessage.getBody());
        return new ByteArrayInputStream(cryptoService.decryptForByte(encrypt));
      }

      @Override
      public HttpHeaders getHeaders() {
        return inputMessage.getHeaders();
      }
    };
  }

  /**
   * Invoked third (and last) after the request body is converted to an Object.
   * @param body set to the converter Object before the first advice is called
   * @param inputMessage the request
   * @param parameter the target method parameter
   * @param targetType the target type, not necessarily the same as the method parameter type, e.g. for {@code
   *     HttpEntity<String>}.
   * @param converterType the converter used to deserialize the body
   * @return the same body or a new instance
   */
  @Override
  public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
  }

  /**
   * Invoked second (and last) if the body is empty.
   * @param body usually set to {@code null} before the first advice is called
   * @param inputMessage the request
   * @param parameter the method parameter
   * @param targetType the target type, not necessarily the same as the method parameter type, e.g. for {@code
   *     HttpEntity<String>}.
   * @param converterType the selected converter type
   * @return the value to use or {@code null} which may then raise an {@code HttpMessageNotReadableException} if the
   *     argument is required.
   */
  @Override
  public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
  }
}
